#pragma once

#include <map>  //  std::map
#include <string>  //  std::string
#include <regex>  //  std::regex, std::regex_match
#include <memory>  //  std::make_unique, std::unique_ptr
#include <vector>  //  std::vector
#include <cctype>  //  std::toupper

namespace sqlite_orm {
    using int64 = sqlite_int64;
    using uint64 = sqlite_uint64;

    //  numeric and real are the same for c++
    enum class sqlite_type {
        INTEGER,
        TEXT,
        BLOB,
        REAL,
    };

    /**
     *  @param str case doesn't matter - it is uppercased before comparing.
     */
    inline std::unique_ptr<sqlite_type> to_sqlite_type(const std::string &str) {
        auto asciiStringToUpper = [](std::string &s) {
            std::transform(s.begin(), s.end(), s.begin(), [](char c) {
                return std::toupper(c);
            });
        };
        auto upperStr = str;
        asciiStringToUpper(upperStr);

        static std::map<sqlite_type, std::vector<std::regex>> typeMap = {
            {sqlite_type::INTEGER,
             {
                 std::regex("INT"),
                 std::regex("INT.*"),
                 std::regex("TINYINT"),
                 std::regex("SMALLINT"),
                 std::regex("MEDIUMINT"),
                 std::regex("BIGINT"),
                 std::regex("UNSIGNED BIG INT"),
                 std::regex("INT2"),
                 std::regex("INT8"),
             }},
            {sqlite_type::TEXT,
             {
                 std::regex("CHARACTER\\([[:digit:]]+\\)"),
                 std::regex("VARCHAR\\([[:digit:]]+\\)"),
                 std::regex("VARYING CHARACTER\\([[:digit:]]+\\)"),
                 std::regex("NCHAR\\([[:digit:]]+\\)"),
                 std::regex("NATIVE CHARACTER\\([[:digit:]]+\\)"),
                 std::regex("NVARCHAR\\([[:digit:]]+\\)"),
                 std::regex("CLOB"),
                 std::regex("TEXT"),
             }},
            {sqlite_type::BLOB,
             {
                 std::regex("BLOB"),
             }},
            {sqlite_type::REAL,
             {
                 std::regex("REAL"),
                 std::regex("DOUBLE"),
                 std::regex("DOUBLE PRECISION"),
                 std::regex("FLOAT"),
                 std::regex("NUMERIC"),
                 std::regex("DECIMAL\\([[:digit:]]+,[[:digit:]]+\\)"),
                 std::regex("BOOLEAN"),
                 std::regex("DATE"),
                 std::regex("DATETIME"),
             }},
        };
        for(auto &p: typeMap) {
            for(auto &r: p.second) {
                if(std::regex_match(upperStr, r)) {
                    return std::make_unique<sqlite_type>(p.first);
                }
            }
        }

        return {};
    }
}
